using System;
namespace ch.ethz.ssh2.crypto.cipher
{
	
	/// <summary> CipherOutputStream.
	/// 
	/// </summary>
	/// <author>  Christian Plattner
	/// </author>
	/// <version>  2.50, 03/15/10
	/// </version>
	public class CipherOutputStream
	{
		private void  InitBlock()
		{
			out_buffer = new byte[BUFF_SIZE];
		}
		internal BlockCipher currentCipher;
		internal System.IO.Stream bo;
		internal byte[] buffer;
		internal byte[] enc;
		internal int blockSize;
		internal int pos;
		
		/*
		* We cannot use java.io.BufferedOutputStream, since that is not available
		* in J2ME. Everything could be improved here alot.
		*/
		
		//UPGRADE_NOTE: Final was removed from the declaration of 'BUFF_SIZE '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
		internal int BUFF_SIZE = 2048;
		//UPGRADE_NOTE: The initialization of  'out_buffer' was moved to method 'InitBlock'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
		internal byte[] out_buffer;
		internal int out_buffer_pos = 0;
		
		public CipherOutputStream(BlockCipher tc, System.IO.Stream bo)
		{
			InitBlock();
			this.bo = bo;
			changeCipher(tc);
		}
		
		private void  internal_write(byte[] src, int off, int len)
		{
			while (len > 0)
			{
				int space = BUFF_SIZE - out_buffer_pos;
				int copy = (len > space)?space:len;
				
				Array.Copy(src, off, out_buffer, out_buffer_pos, copy);
				
				off += copy;
				out_buffer_pos += copy;
				len -= copy;
				
				if (out_buffer_pos >= BUFF_SIZE)
				{
					bo.Write(out_buffer, 0, BUFF_SIZE);
					out_buffer_pos = 0;
				}
			}
		}
		
		private void  internal_write(int b)
		{
			out_buffer[out_buffer_pos++] = (byte) b;
			if (out_buffer_pos >= BUFF_SIZE)
			{
				bo.Write(out_buffer, 0, BUFF_SIZE);
				out_buffer_pos = 0;
			}
		}
		
		public virtual void  flush()
		{
			if (pos != 0)
				throw new System.IO.IOException("FATAL: cannot flush since crypto buffer is not aligned.");
			
			if (out_buffer_pos > 0)
			{
				bo.Write(out_buffer, 0, out_buffer_pos);
				out_buffer_pos = 0;
			}
			bo.Flush();
		}
		
		public virtual void  changeCipher(BlockCipher bc)
		{
			this.currentCipher = bc;
			blockSize = bc.BlockSize;
			buffer = new byte[blockSize];
			enc = new byte[blockSize];
			pos = 0;
		}
		
		private void  writeBlock()
		{
			try
			{
				currentCipher.transformBlock(buffer, 0, enc, 0);
			}
			catch (System.Exception e)
			{
				throw (System.IO.IOException) new System.IO.IOException("Error while decrypting block.",e);
			}
			
			internal_write(enc, 0, blockSize);
			pos = 0;
		}
		
		public virtual void  write(byte[] src, int off, int len)
		{
			while (len > 0)
			{
				int avail = blockSize - pos;
				int copy = System.Math.Min(avail, len);
				
				Array.Copy(src, off, buffer, pos, copy);
				pos += copy;
				off += copy;
				len -= copy;
				
				if (pos >= blockSize)
					writeBlock();
			}
		}
		
		public virtual void  write(int b)
		{
			buffer[pos++] = (byte) b;
			if (pos >= blockSize)
				writeBlock();
		}
		
		public virtual void  writePlain(int b)
		{
			if (pos != 0)
				throw new System.IO.IOException("Cannot write plain since crypto buffer is not aligned.");
			internal_write(b);
		}
		
		public virtual void  writePlain(byte[] b, int off, int len)
		{
			if (pos != 0)
				throw new System.IO.IOException("Cannot write plain since crypto buffer is not aligned.");
			internal_write(b, off, len);
		}
	}
}